#include "JScriptEdit.h"
#include "JScriptHighlighter.h"
#include "JLog.h"

#include <QPainter>
#include <QKeyEvent>
#include <QScrollBar>
#include <QTextLayout>
#include <QTextDocument>
#include <QAbstractTextDocumentLayout>

JScriptEdit::JScriptEdit(QWidget *parent)
	: QTextEdit(parent), mTab(0)
{
	setMouseTracking(true);
	setFrameShape(QFrame::NoFrame);
	setFrameShadow(QFrame::Plain);
	setWordWrapMode(QTextOption::NoWrap);
	setTabStopWidth(30);

	QFont font;
	font.setFamily(QString::fromUtf8("Courier New"));
	font.setPointSize(9);
	setFont(font);

	connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
	connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotViewportUpdate()));

	/*LineNumberPanel *numpanel = new LineNumberPanel(this);
	numpanel->attach(this);
	LineFoldPanel *foldpanel = new LineFoldPanel(this);
	foldpanel->attach(this);*/

	JScriptHighlighter *highlighter = new JScriptHighlighter(document());
}

JScriptEdit::~JScriptEdit()
{

}

void JScriptEdit::keyPressEvent( QKeyEvent *e )
{
	bool returnHandled = false;
	bool tabHandled = false;
	bool backHandled = false;
	switch (e->key())
	{
	case Qt::Key_Tab:
		tabHandled = true;
		break;
	case Qt::Key_Backspace:
		backHandled = true;
		break;
	case Qt::Key_Return:
		returnHandled = true;
		break;
	default:
		break;
	}

	QTextEdit::keyPressEvent(e);

	/*QTextCursor textCur   = textCursor();
	int pos = textCur.position();
	textCur.setPosition(textCur.block().position());
	textCur.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
	int startPos = textCur.selectionStart();
	int offset = pos-startPos;
	QString text = textCur.selectedText();

	int cur = offset;
	while (cur--) {
		QChar c = text.at(cur);
		if (c == '\t') {
			JLOG.Log("new tab added");
		} else {
			break;
		}
	}*/

	if (returnHandled)
		for (int i=0; i<mTab; i++) textCursor().insertText("\t");
}

void JScriptEdit::paintEvent( QPaintEvent * evt)
{
	QColor lineColor = QColor::fromCmykF(0.05,0.05,0.05,0.0);
	QPainter painter(viewport());
	if (lineColor.isValid())
	{
		QRect r = JScriptEdit::cursorRect();
		r.setX(3);
		r.setWidth(viewport()->width()-5);
		painter.fillRect(r, QBrush(lineColor));
	}
	painter.end();
	QTextEdit::paintEvent(evt);
}

void JScriptEdit::braceMatching()
{
	QTextEdit::ExtraSelection mSelection;
	QList<QTextEdit::ExtraSelection> extraSelections;
	extraSelections.clear();
	setExtraSelections(extraSelections);
	QColor lineColor = QColor(Qt::yellow).lighter(160);
	mSelection.format.setBackground(lineColor);

	QTextDocument *doc = document();
	QTextCursor cursor = textCursor();
	QTextCursor beforeCursor = cursor;

	cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
	QString brace = cursor.selectedText();

	beforeCursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
	QString beforeBrace = beforeCursor.selectedText();

	if ((brace != "{") 
		&& (brace != "}") 
		&& (brace != "[") 
		&& (brace != "]") 
		&& (brace != "(")
		&& (brace != ")")) 
	{
		if ((beforeBrace == "{") 
			|| (beforeBrace == "}") 
			|| (beforeBrace == "[")
			|| (beforeBrace == "]")
			|| (beforeBrace == "(")
			|| (beforeBrace == ")")) 
		{
			cursor = beforeCursor;
			brace = cursor.selectedText();
		} else {
			return;
		}
	}

	QString openBrace;
	QString closeBrace;

	if ((brace == "{") || (brace == "}")) {
		openBrace = "{";
		closeBrace = "}";
	}

	if ((brace == "[") || (brace == "]")) {
		openBrace = "[";
		closeBrace = "]";
	}

	if ((brace == "(") || (brace == ")")) {
		openBrace = "(";
		closeBrace = ")";
	}

	if (brace == openBrace) {
		QTextCursor cursor1 = doc->find(closeBrace, cursor);
		QTextCursor cursor2 = doc->find(openBrace, cursor);
		if (cursor2.isNull()) {
			mSelection.cursor = cursor;
			extraSelections.append(mSelection);
			mSelection.cursor = cursor1;
			extraSelections.append(mSelection);
			setExtraSelections(extraSelections);
		} else {

			while (cursor1.position() > cursor2.position()) {
				cursor1 = doc->find(closeBrace, cursor1);
				cursor2 = doc->find(openBrace, cursor2);
				if (cursor2.isNull()) {
					break;
				}
			}
			mSelection.cursor = cursor;
			extraSelections.append(mSelection);
			mSelection.cursor = cursor1;
			extraSelections.append(mSelection);
			setExtraSelections(extraSelections);
		}
	} else {
		if (brace == closeBrace) {
			QTextCursor cursor1 = doc->find(openBrace, cursor, QTextDocument::FindBackward);
			QTextCursor cursor2 = doc->find(closeBrace, cursor, QTextDocument::FindBackward);
			if (cursor2.isNull()) {
				mSelection.cursor = cursor;
				extraSelections.append(mSelection);
				mSelection.cursor = cursor1;
				extraSelections.append(mSelection);
				setExtraSelections(extraSelections);
			} else {
				while (cursor1.position() < cursor2.position()) {
					cursor1 = doc->find(openBrace, cursor1, QTextDocument::FindBackward);
					cursor2 = doc->find(closeBrace, cursor2, QTextDocument::FindBackward);
					if (cursor2.isNull()) {
						break;
					}
				}
				mSelection.cursor = cursor;
				extraSelections.append(mSelection);
				mSelection.cursor = cursor1;
				extraSelections.append(mSelection);
				setExtraSelections(extraSelections);
			}
		}
	}
}

void JScriptEdit::slotCursorPositionChanged()
{
	viewport()->update();
	braceMatching();
}

void JScriptEdit::slotViewportUpdate()
{
	viewport()->update();
}

void JScriptEditPanel::attach( QTextEdit *e )
{
	if ( pEdit )
	{
		disconnect(	pEdit->verticalScrollBar(),
			SIGNAL( valueChanged(int) ),
			this,
			SLOT  ( update() ) );

		if ( pEdit->document() )
			disconnect(	pEdit->document()->documentLayout(),
			SIGNAL( update(const QRectF&) ),
			this,
			SLOT  ( update() ) );
	}

	pEdit = e;

	if ( pEdit )
	{
		connect(pEdit->verticalScrollBar(), SIGNAL(valueChanged(int)),
			this, SLOT(update()));

		if ( pEdit->document() )
			connect(pEdit->document()->documentLayout(),
			SIGNAL( update(const QRectF&) ),
			this,
			SLOT  ( update() ) );
	}
}

void JScriptEditPanel::paintEvent( QPaintEvent *e )
{
	if ( !pEdit || !pEdit->document() )
	{
		e->ignore();
		return;
	}

	e->accept();

	QPainter p(this);
	paint(&p, pEdit);
}

LineNumberPanel::LineNumberPanel( QWidget *p /*= 0*/ )
: JScriptEditPanel(p)
{
	setAutoFillBackground(true);
	setFixedWidth(30);
}

void LineNumberPanel::paint( QPainter *p, QTextEdit *e )
{
	QFont font = p->font();
	font.setFamily(QString::fromUtf8("Courier New"));
	font.setPointSize(9);
	p->setFont(font);
	//const QFontMetrics fm( e->document()->defaultFont() );
	const QFontMetrics fm(font);
	int posY,
		n = 1,
		ls = fm.lineSpacing(),
		contentsY = e->verticalScrollBar()->value();

	qreal pageBottom = contentsY + e->viewport()->height();

	QString txt;
	QPointF position;
	QTextLayout *layout;
	QRectF boundingRect;
	QBlockData  * bd;
	for (QTextBlock block = e->document()->begin();
		block.isValid(); 
		block = block.next() )
	{
		bd = QBlockData::data(block);
		layout = block.layout();

		boundingRect = layout->boundingRect();
		position = layout->position();

		if (position.y() + boundingRect.height() < contentsY)
		{
			if ( bd ) n+=bd->collapsedBlock;  n++;
			continue;
		}

		if (position.y() > pageBottom)
			break;
		posY = qRound(position.y());

		txt = QString::number(n);//+tr(" ");
		QPen pen(p->pen());
		pen.setColor(QColor(0,0,0));
		p->setPen(pen);

		p->drawText(0, posY - contentsY, width(), ls, Qt::AlignRight | Qt::AlignTop,/*Qt::AlignVCenter,*/txt);

		if ( bd ) n+=bd->collapsedBlock;  n++;
	}
}

LineFoldPanel::LineFoldPanel( QWidget *p /*= 0*/ )
: JScriptEditPanel(p)
{
	setAutoFillBackground(true);
	setFixedWidth(16);
}

void LineFoldPanel::mousePressEvent( QMouseEvent *e )
{
	if ( e->button() != Qt::LeftButton )
		return;

	QMap<QTextBlock, QRect>::iterator i;

	for ( i = collapsable.begin(); i != collapsable.end(); i++ )
	{
		if (!i->contains(e->pos()))
			continue;
		QTextBlock b = i.key();
		mouseclk( b );
	}
	update();
	QWidget *w = dynamic_cast<QWidget*>(editor());

	if (w) w->setFocus();
}

void LineFoldPanel::keyPressEvent( QKeyEvent *e )
{

}

void LineFoldPanel::paint( QPainter *p, QTextEdit *e )
{
	setFixedWidth(16);
	collapsable.clear();
	match(e);
	//QPen pen = p->pen ();
	//pen.setColor( TextColor );
	//p->setPen ( pen );

	QFontMetrics * fm = NULL;
	bool bVisible = false;
	//const QFontMetrics fm( e->document()->defaultFont() );

	int pos, posY,
		n = 1,
		ls,// = fm.lineSpacing(),
		contentsY = e->verticalScrollBar()->value();

	qreal pageBottom = contentsY + e->viewport()->height();

	QPointF position;
	QTextLayout *layout;
	QRectF boundingRect;
	QTextBlock cursorBlock = e->textCursor().block();
	int bound;
	int mid;
	QTextBlock nextBlock;
	int nextY;
	QTextBlock block;
	for (block = e->document()->begin();
		block.isValid(); 
		block = block.next(), ++n )
	{
		fm = new QFontMetrics( block.charFormat ().font () );
		ls = fm->lineSpacing();

		layout = block.layout();

		boundingRect = layout->boundingRect();
		position = layout->position();
		nextBlock = block.next();
		if (nextBlock.isValid()) 
		{
			nextY = ( int )((nextBlock.layout()->position()).y() - position.y());
		}
		else nextY = ls; //if ( nextY > ls ) nextY -= ls;
		if ( position.y() > pageBottom )
			break;

		posY = qRound(position.y());
		pos = posY - contentsY;
		bVisible = (position.y() + boundingRect.height()) >= contentsY;
		int s = BlockUserState( block );
		if ( !bVisible ) continue;

		//bound = ( ls   - 11) / 2;
		bound = ( int ) (( boundingRect.height() - 11 )/2);
		//bound = ls / 2;
		switch ( s )
		{
		case 0:
			break;
		case 1:			
			p->drawLine(7, pos, 7, pos + nextY);
			//	p->drawLine(8, pos, 8, pos + ls);
			break;
		case 2:

			if (  block.previous ().isValid() && BlockUserState( block.previous ()) )
				p->drawLine(7, pos, 7, pos + bound);	
			collapsable.insert( block, drawIcon(p, e, 3, pos + bound , false));
			p->drawLine(7, pos + bound + 11 , 7, ( int )(pos + bound + 11 + boundingRect.height()/2 - 4));	
			break;
		case 3: 	
			mid = ( int ) (pos + boundingRect.height()/ 2);
			p->drawLine(7, pos, 7, mid);
			p->drawLine(7, mid, 12 + ls, mid);
			break;
		case 4: 	

			mid = ( int ) (pos + boundingRect.height() / 2);
			p->drawLine(7, pos, 7, ( int )(pos + boundingRect.height()) );
			p->drawLine(7, mid, 12, mid);

			break;
		case 5:
			if (  block.previous ().isValid() && BlockUserState( block.previous ()) )
				p->drawLine(7, pos, 7, pos + bound);	
			collapsable.insert( block, drawIcon(p, e, 3, pos + bound, true));
			if (  block.next ().isValid() && BlockUserState( block.next ()) )
				p->drawLine(7, pos + bound + 11 , 7, ( int )(pos + bound + 11 + boundingRect.height()/2 - 4));	
			break;
		}
	}
	delete fm;
}

void LineFoldPanel::expand( QTextBlock & b )
{
	QTextCursor cursor1 = editor()->textCursor ();
	cursor1.setPosition(b.position());
	editor()->setTextCursor( cursor1 );
	if ( b.position() < editor()->textCursor ().position() ) return;

	QTextCursor cursor(b);  
	QString txt = b.text();
	int posinStartBlock = txt.indexOf("{") + 1;
	cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, posinStartBlock);

	QTextBlock block;

	QPair<QString, QBlockData*> line;
	QBlockData *bd = QBlockData::data(b);
	QBlockData * data;

	int count= bd->folded->lines.count();

	if ( count > 0 )
	{
		foreach ( line, bd->folded->lines )
		{
			block = cursor.block();
			cursor.insertText(line.first);
			data = line.second ;
			if ( data ) block.setUserData( data );
		}
	}
	if ( bd->folded ) delete bd->folded;
	bd->folded = 0;
	bd->collapsedBlock = 0;
}

void LineFoldPanel::expandAll()
{
	QTextBlock block;
	bool all = true;
	QBlockData *bd;
	int exp;
	while ( all )
	{
		exp = 0;
		for ( block = editor()->document()->begin();	block.isValid(); 	block = block.next() )
		{
			bd = QBlockData::data(block);
			if ( bd && bd->folded && bd->folded->lines.count() > 0 ) 
			{ 
				expand(block); 
				exp++; 
				break; 
			}
		}
		if ( exp == 0 ) break;
	}
}

bool LineFoldPanel::collapsed( QTextBlock & b )
{
	QTextCursor cursor = editor()->textCursor ();
	cursor.setPosition(b.position());
	editor()->setTextCursor( cursor );
	if ( b.position() < editor()->textCursor ().position() ) return false;

	int open = 0, i;
	QString txt;
	QChar ch;
	bool find = false;
	bool ok = false;
	QTextBlock block;
	QTextBlock blockEnd;
	txt = b.text();
	int posinEndBlock = 0;
	int collapsedBlock;
	int posinStartBlock = txt.indexOf("{") + 1;
	int strOpen = 0;
	for ( block = b;	block.isValid(); 	block = block.next() )
	{
		txt = block.text ();
		for( i = 0; i < txt.length(); i++ )
		{
			ch = txt.at(i);
			if ( ch == '"' ) {  if ( strOpen == 0 ) strOpen++; else strOpen = 0; } 
			if ( ch == '{' ) { if ( strOpen == 0 ) {open++; find = true;}   }
			if ( ch == '}' ) { if ( strOpen == 0 ) open--;    } 
			if ( open == 0 && find ) { ok = true; blockEnd = block; posinEndBlock = i ; break; }
		}
		if ( ok ) break;
	}
	if ( !ok ) {  return false; }
	QString str;
	collapsedBlock = 0;
	QFoldedText *fold = new QFoldedText;
	QBlockData  * bd = new QBlockData;
	//QBlockData::data(b);
	QBlockData	*old , *data;
	QTextCursor cursorbegin(b);  
	QTextCursor cursorend(blockEnd); 

	cursorbegin.beginEditBlock();
	for ( block = b;	block.isValid(); 	block = block.next() )
	{
		txt = block.text ();
		if ( block == b ) {  str = txt.right( txt.length() - posinStartBlock );  
		fold->addLine(str + "\n", 0); continue;}
		if ( block == blockEnd ) {  str = txt.left( posinEndBlock ); 
		fold->addLine(str, 0);   collapsedBlock++; break; }
		data = 0; old = QBlockData::data(block);
		if ( old ) {
			data = new QBlockData;
			data->folded = old->folded;
			collapsedBlock += old->collapsedBlock + 1;
			data->collapsedBlock = old->collapsedBlock;
		}

		fold->addLine(txt + "\n", data);
	}

	cursorbegin.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, posinStartBlock);
	cursorend.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, posinEndBlock);
	//int cur1 = cursorend.position() - cursorbegin.position() + posinEndBlock - 1 ;
	cursorbegin.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, cursorend.position() - cursorbegin.position());
	cursorbegin.removeSelectedText ();
	cursorbegin.endEditBlock();
	bd->folded = fold;

	bd->collapsedBlock =  collapsedBlock;
	bd->setToBlock(b);
	return true;
}

void LineFoldPanel::mouseclk( QTextBlock & b )
{
	QBlockData * blkData;	
	int pos = b.position();
	blkData = QBlockData::data(b);
	//EnhEditor->setUndoRedoEnabled(false);
	if ( blkData )
	{

		if ( blkData->folded && blkData->folded->lines.count() > 0 ) 
		{
			expand(b);
			//EnhEditor->CreateExpandedText( pos );
			//EnhEditor->CopyDocument();
		}
		else 
		{
			if (collapsed(b))
			{
				//EnhEditor->CreateCollapsedText(pos);
				//EnhEditor->CopyDocument();
			}
		}

	}
	//EnhEditor->setUndoRedoEnabled(true);
}

QRect LineFoldPanel::drawIcon( QPainter *p, QTextEdit *e, int x, int y, bool toExpand )
{
	QRect symbolRect(x, y+1, 8, 8);

	p->save();
	//p->setRenderHint(QPainter::Antialiasing);
	p->drawRect(symbolRect);
	p->restore();

	if ( toExpand )
	{
		p->drawLine(x + 2, y + 5, x + 6, y + 5);
		p->drawLine(x + 4, y + 3, x + 4, y + 7);
	} else {
		p->drawLine(x + 2, y + 5, x + 6, y + 5);
	}

	return symbolRect;
}

void LineFoldPanel::match( QTextEdit *e )
{
	int open = 0;
	QString txt, chPrev;
	QChar ch;
	int i = 0;
	int prev_state;
	int prev_open = 0;
	QTextBlock prev;
	QBlockData * blkData;

	FoldType = FOLD_OFF;
	for (QTextBlock block = e->document()->begin();
		block.isValid(); 
		block = block.next() )
	{
		prev_open = open;

		setBlockUserState( block, 0 );

		blkData = QBlockData::data(block);
		if ( !blkData ) { blkData = new QBlockData; blkData->setToBlock(block); }
		txt = block.text ();
		for( i = 0; i < txt.length(); i++ )
		{
			if ( i > 0 ) chPrev =  txt.mid (i - 1, 1 ); else chPrev = "";
			ch = txt.at(i);
			switch ( FoldType )
			{
			case FOLD_CHAR:
				if ( ch == '\'' ) {	FoldType = FOLD_OFF; break; }
				break;
			case FOLD_OFF:
				if ( ch == '\'' ) { FoldType = FOLD_CHAR; break; }
				if ( ch == '"' )
				{
					FoldType = FOLD_STRING;
					break;
				}
				if ( i > 0 && ( ch == '{' || ch == '}' ) && txt.at(i -1) == '\'' && ( (i+1) <txt.length() && txt.at(i +1)== '\''))
				{
					i++;
					break;
				}
				if (i > 0 && ch == '/' && txt.at(i -1) == '/')
				{
					i = txt.length(); break;
				}
				if (i > 0 && ch == '*' && txt.at(i -1) == '/')
				{
					FoldType = FOLD_MULTILINECOMMENT;
					break;
				}
				if ( ch == '{'  ) open++;
				if ( ch == '}' ) open--;
				break;
			case FOLD_SINGLELINECOMMENT:

				break;
			case FOLD_MULTILINECOMMENT:
				if (i > 0 && ch == '/' && txt.at(i -1) == '*') FoldType = FOLD_OFF;
				break;
			case FOLD_STRING:
				if ( ch == '"' && txt.at(i -1) == '\\' && txt.at(i -2) == '\\'/*chPrev !="\\" */) { FoldType = FOLD_OFF; break; }
				else	if ( ch == '"' && txt.at(i -1) != '\\'/*chPrev !="\\" */) 
				{FoldType = FOLD_OFF;
				break;
				}
			}
		}
		prev = block.previous();
		if ( prev.isValid() ) prev_state = BlockUserState(prev); /*prev.userState(); */ else prev_state = 0;

		blkData = QBlockData::data(block);
		if ( blkData && blkData->folded &&  blkData->folded->lines.count() > 0 ) { 
			setBlockUserState( block, 5 ); 
			continue; }


		switch (open)
		{
		case 0: 
			if ( prev_open > open ) setBlockUserState( block, 3 ); 
			if ( prev_state == 0 ) setBlockUserState( block, 0 ); 
			break;
		default:
			if ( prev_open == open ) setBlockUserState( block, 1 ); 
			if ( prev_open < open ) setBlockUserState( block, 2 ); 
			if ( prev_open > open ) setBlockUserState( block, 4 ); 
			break;
		}
	}
}

void LineFoldPanel::setBlockUserState( QTextBlock & b, int state )
{
	QBlockData * blkData = QBlockData::data(b);
	if ( !blkData )  blkData = new QBlockData;    
	blkData->userState = state; 
	blkData->setToBlock(b);
}

int LineFoldPanel::BlockUserState( const QTextBlock & b )
{
	QBlockData * blkData = QBlockData::data(b);
	return blkData->userState;
}